# 【爬虫】用Requests+正则+BeautifulSoup获取今日头条图集【附完整代码】
# 经过调试title有问题
import requests
from urllib.parse import urlencode
from requests import RequestException
import json
from bs4 import BeautifulSoup
import re
from hashlib import md5
from multiprocessing import Pool
import os
from config import *
import time

# 设置全局变量client和db
# client = pymongo.MongoClient(MONGO_URL, connect=False) #多进程时设置connect=False，使每个进程在执行时才启动链接，否则会出现警告，
# db = client[MONGO_DB]

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
    'cookie': 'tt_webid=6788303273558328836; s_v_web_id=k62zuzmj_5NhSMzaM_ChtT_4HhQ_BpGy_BeHXo4kuXL89; WEATHER_CITY=%E5%8C%97%E4%BA%AC; tt_webid=6788303273558328836; csrftoken=7a6a70ae9d8e77dbec081fd30c90b676; __tasessionId=2ermjesmm1580553222148'
}
#该函数用来分析索引解码，获取进入详情页的url。
#我们要传入一个offset（页数）和keyword（搜索的关键字）
def get_page_index(offset, keyword):
    # data中的内容就是我们上图中访问参数，可以直接复制下来，把最后的timestamp参数删掉
    data = {
        'aid': '24',
        'app_name': 'web_search',
        'offset': offset,
        'format': 'json',
        'keyword': keyword,
        'autoload': 'true',
        'count': '20',
        'en_qc': '1',
        'cur_tab': '1',
        'from': 'search_tab',
        'pd': 'synthesis'
    }

    # 构造访问的url
    url = 'https://www.toutiao.com/api/search/content/?' + urlencode(data)

    # 开始访问url
    try:
        response = requests.get(url, headers=header)
        if response.status_code == 200:
            # 访问成功
            return response.text
        return None
    except RequestException:
        print('请求索引页面出错')
        return None


# 该函数用来解析返回的json数据中所含有的指向详情页的链接
# 返回每一个页面的链接
def parse_page_index(html):
    # 解析json
    data = json.loads(html)

    # 判断data是否为空 且 判断‘data’这个属性是否在返回的数据中
    if data and 'data' in data.keys():
        if data.get('data'):
            # 循环遍历data中所有的项，返回article_url
            for item in data.get('data'):
                yield item.get('article_url')


# 将详情页面的url传入到该函数中，爬取详情界面的内容
def get_page_detail(url):
    try:
        response = requests.get(url, headers=header)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print('请求页面详情出错', url)
        return None


# 这里传入html（页面返回的数据），url（详情页面的url，用于保存进mongodb中）
def parse_page_detail(html, url):
    # 首先使用BeautifulSoup解析网页，并获得title
    soup = BeautifulSoup(html, 'lxml')
    # 这个真的是一个坑，所以如果网页标签head下面的title标签为空，要从json中获取
    title = soup.head.title
    if title is not None:
        title = title.string
    else:
        title_pattern = re.compile('title: (.*?),', re.S)
        title = re.search(title_pattern, html)
        if title is not None:
            title = title.group(1)
        else:
            title = '无标题'
    title = str(title)
    # 有时候遇到title为空的情况  如这个链接https://www.toutiao.com/a6637416179797328391/
    # print(type(title1))
    # print(title1)
    # title = soup.select('title')[0].get_text()
    # print(type(title))
    # print(title)

    # compile返回一个正则表达式对象
    pattern = re.compile('gallery: JSON.parse\((.*?)\)', re.S)
    result = re.search(pattern, html)

    # 当匹配的结果不为空的时候
    if result:
        # 注意这里要用json.loads()解析两次
        # 返回的是字符串的格式
        data = json.loads(json.loads(result.group(1)))
        # print(type(data))
        # print(data)
        # sub_images对应的就是图片的url
        if data and 'sub_images' in data.keys():
            sub_images = data.get('sub_images')
            # 将所有图片的url放进集合中
            # [{'key':'value'},{'key':'value'}]
            images_url = [item.get('url') for item in sub_images]
            # 循环遍历集合，下载图片
            for image_url in images_url:
                # 下载函数，后面会解释
                download_image(image_url, title)
            # 最后返回一个dict，title为标题，url为该详情页链接，iamges是该图集所有图片链接的集合
            return {
                'title': title,
                'url': url,
                'images': images_url
            }
    return None


# 传入图片的url，和图片标题（用于创建文件夹）
def download_image(url, title):
    try:
        response = requests.get(url, headers=header)
        if response.status_code == 200:
            # 这里要传response.content content是网页的二进制内容，text则是文本
            save_image(response.content, title)
    except RequestException:
        print('请求图片出错', url)
        return None


# 该函数用于保存图片
def save_image(content, title):
    # 图片保存文件夹的路径
    # os.getcwd()获取当前路径
    dir_path = '{0}/{1}/{2}'.format(os.getcwd(), 'toutiao', title)
    # 若文件夹不存在，则创建
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)
    # 图片保存的路径，这里使用md5加密算法对文件内容加密，一个文件对应一个md5码，以保证图片不会重复
    file_path = dir_path + '/{0}.{1}'.format(md5(content).hexdigest(), 'jpg')
    # 文件不存在就写入
    if not os.path.exists(file_path):
        with open(file_path, 'wb') as f:
            f.write(content)
            f.close()
# 没有安装mongDB和pymongo也是用不了这个函数，会报错
# def save_to_mongo(result):
#     if result and db[MONGO_TABLE].insert(result):
#         print('存储到MongoDB成功', result)
#         return True
#     return False


# main函数为执行调用函数
# 传入offset参数，用户翻页，若想改变搜索条件，到config.py中修改KEYWORD的值
def main(offset):
    # KEYWORD定义在了config.py文件中，目前值为'街拍'
    html = get_page_index(offset, KEYWORD)
    # 爬取并解析索引界面，获得详情界面的url
    for url in parse_page_index(html):
        # 当获取的url不为空时，开始解析
        if url is not None:
            # 获取每个页面的详情内容
            html = get_page_detail(url)
            if html:
                # 解析每个页面的详情内容
                # 这里如果不保存在url中就不用传入url
                result = parse_page_detail(html, url)
                if result is not None:
                    print(result)
                else:
                    print('result is None')
                # 将返回内容存入到mongoDB中
                # save_to_mongo(result)

if __name__ == '__main__':
    group = [i*20 for i in range(0, 5)]
    pool = Pool()
    # 开启多线程
    start = time.clock()
    pool.map(main, group)
    end = time.clock()
    print('一共运行了{}秒'.format(end - start))
    # 单线程这么写
    '''
    for i in range(1,5):
        main(i*20)
    '''